package step

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"github.com/wanchain/go-mpc/common/hexutil"
	"github.com/wanchain/go-mpc/log"
	"github.com/wanchain/go-mpc/storeman/osmconf"
	schcomm "github.com/wanchain/go-mpc/storeman/schnorrcomm"
	mpcprotocol "github.com/wanchain/go-mpc/storeman/storemanmpc/protocol"
	"math/big"
	"strconv"
)

type MpcSStepUni struct {
	BaseMpcStep
	resultKeys   []string
	signNum      int
	SShareErrNum int

	sshareOKIndex []uint16
	sshareKOIndex []uint16
	sshareNOIndex []uint16
}

func CreateMpcSStepUni(totalNum int, signNum int, resultKeys []string) *MpcSStepUni {

	log.SyslogInfo("CreateMpcSStepUni begin")
	mpc := &MpcSStepUni{*CreateBaseMpcStep(totalNum, signNum), resultKeys, signNum, 0,
		make([]uint16, 0), make([]uint16, 0), make([]uint16, 0)}

	for i := 0; i < signNum; i++ {
		mpc.messages[i] = createSGeneratorUni(i, mpc.schnorrMpcer)
	}

	return mpc
}

func (msStep *MpcSStepUni) CreateMessage() []mpcprotocol.StepMessage {
	log.SyslogDebug("MpcSStepUni::CreateMessage begin", "ctx", msStep.GetMpcContextId())
	// sshare, sig of sshare

	message := make([]mpcprotocol.StepMessage, 1)
	message[0].MsgCode = mpcprotocol.MPCMessage
	message[0].PeerID = nil
	message[0].Data = make([]big.Int, 3*msStep.signNum)

	for i := 0; i < msStep.signNum; i++ {
		pointer := msStep.messages[i].(*mpcSGeneratorUni)

		h := sha256.Sum256(pointer.seed.Bytes())
		prv, err := osmconf.GetOsmConf().GetSelfPrvKey()
		if err != nil {
			log.SyslogErr("MpcSStepUni::CreateMessage", "GetSelfPrvKey error", err.Error(), "ctx", msStep.GetMpcContextId())
			return nil
		}
		r, s, _ := schcomm.SignInternalData(prv, h[:])

		if schcomm.MaliceSSig {
			message[0].Data[3*i] = *schcomm.BigOne
		} else {
			message[0].Data[3*i] = pointer.seed
		}

		message[0].Data[3*i+1] = *r

		if schcomm.MaliceSContent {
			message[0].Data[3*i+2] = *schcomm.BigOne
		} else {
			message[0].Data[3*i+2] = *s
		}
	}

	return message
}

func (msStep *MpcSStepUni) HandleMessage(msg *mpcprotocol.StepMessage) bool {
	log.SyslogInfo("MpcSStepUni::HandleMessage", "MpcSStep.HandleMessage begin, peerID", msg.PeerID.SlimString(), "ctx", msStep.GetMpcContextId())

	mapOKIndex := make(map[uint16]bool)
	for i := 0; i < msStep.signNum; i++ {
		pointer := msStep.messages[i].(*mpcSGeneratorUni)
		_, exist := pointer.message[*msg.PeerID]
		if exist {
			log.SyslogErr("MpcSStepUni::HandleMessage", "MpcSStep.HandleMessage, get msg from seed fail. peer", msg.PeerID.SlimString(), "ctx", msStep.GetMpcContextId())
			return false
		}

		sshare := msg.Data[3*i]
		r := msg.Data[3*i+1]
		s := msg.Data[3*i+2]

		_, grpIdString, _ := msStep.BaseStep.GetGrpId()

		senderPk, _ := osmconf.GetOsmConf().GetPKByNodeId(grpIdString, msg.PeerID)

		err := schcomm.CheckPK(senderPk)
		if err != nil {
			log.SyslogErr("MpcSStepUni::HandleMessage", "HandleMessage", err.Error(), "ctx", msStep.GetMpcContextId())
		}

		senderIndex, _ := osmconf.GetOsmConf().GetInxByNodeId(grpIdString, msg.PeerID)

		// 1. check sig
		h := sha256.Sum256(sshare.Bytes())
		bVerifySig := schcomm.VerifyInternalData(senderPk, h[:], &r, &s)

		if !bVerifySig {
			// save error and send judge info to leader
			log.SyslogErr("MpcSStepUni::HandleMessage", " check sig fail", "ctx", msStep.GetMpcContextId())

		} else {
			log.SyslogInfo("MpcSStepUni::HandleMessage check sig of sshare successfully", " senderIndex", senderIndex, "ctx", msStep.GetMpcContextId())
		}

		// 2. check content
		selfIndex, err := osmconf.GetOsmConf().GetSelfInx(grpIdString)
		if err != nil {
			log.SyslogInfo("MpcSStepUni::HandleMessage", " GetSelfInx", err.Error(), "ctx", msStep.GetMpcContextId())
		}

		log.SyslogInfo("MpcSStepUni::HandleMessage", " senderIndex", senderIndex, "selfIndex", selfIndex, "ctx", msStep.GetMpcContextId())

		rpkShare, err := msStep.getRPkShare(senderIndex, i)
		if err != nil {
			log.SyslogErr("MpcSStepUni::HandleMessage getRPkShare", " error", err.Error(), "ctx", msStep.GetMpcContextId())
		}

		gpkShare, err := msStep.getGPKShare(senderIndex)
		if err != nil {
			log.SyslogErr("MpcSStepUni::HandleMessage getGPKShare", " error", err.Error(), "ctx", msStep.GetMpcContextId())
		}

		m, err := msStep.getm(i)
		if err != nil {
			log.SyslogErr("MpcSStepUni::HandleMessage getm", " error", err.Error(), "ctx", msStep.GetMpcContextId())
		}

		bContentCheck, err := msStep.checkContent(&sshare, m, rpkShare, gpkShare)
		if err != nil {
			log.SyslogErr("MpcSStepUni::HandleMessage checkContent", " error", err.Error(), "ctx", msStep.GetMpcContextId())
		}

		if bContentCheck {
			log.SyslogInfo("MpcSStepUni::HandleMessage check content of sshare successfully", " senderIndex", senderIndex, "ctx", msStep.GetMpcContextId())
		} else {
			log.SyslogErr("MpcSStepUni::HandleMessage check content of sshare fail", " senderIndex", senderIndex, "ctx", msStep.GetMpcContextId())
		}

		// 3. write error sshare
		// 3.1 write error count
		// 3.2 write error info
		oldSShareErrNum := msStep.SShareErrNum
		if !bContentCheck || !bVerifySig {
			msStep.SShareErrNum += 1

			msStep.sshareKOIndex = append(msStep.sshareKOIndex, senderIndex)

			sshareErrInfo := make([]big.Int, 6)
			// sendIndex, rvcIndex, sshare, r, s
			sshareErrInfo[0] = *big.NewInt(0).SetInt64(int64(senderIndex))
			sshareErrInfo[1] = *big.NewInt(0).SetInt64(int64(selfIndex))
			sshareErrInfo[2] = sshare
			sshareErrInfo[3] = r
			sshareErrInfo[4] = s
			sshareErrInfo[5] = *big.NewInt(0).SetInt64(int64(i))

			// save error info
			keyErrInfo := mpcprotocol.SShareErrInfos + strconv.Itoa(int(msStep.SShareErrNum)-1)
			err := msStep.mpcResult.SetValue(keyErrInfo, sshareErrInfo)
			if err != nil {
				log.SyslogErr("MpcSStepUni::HandleMessage mpcResult.SetValue save fail", " err", err.Error(), "key", keyErrInfo, "ctx", msStep.GetMpcContextId())
			} else {
				if msStep.SShareErrNum > 0 {
					sshareErrInfoStr := fmt.Sprintf("%#v", sshareErrInfo)
					log.SyslogWarning("MpcSStepUni::HandleMessage mpcResult.SetValue save success", "key", keyErrInfo, "value", sshareErrInfoStr, "ctx", msStep.GetMpcContextId())
				}
			}

		} else {
			log.SyslogInfo("MpcSStepUni::HandleMessage check sshare successfully", " senderIndex", senderIndex, "ctx", msStep.GetMpcContextId())
			mapOKIndex[senderIndex] = true
			//msStep.sshareOKIndex = append(msStep.sshareOKIndex, senderIndex)

		}
		pointer.message[*msg.PeerID] = sshare

		// save error number errNum=0:no error.
		if msStep.SShareErrNum > oldSShareErrNum {
			keyErrNum := mpcprotocol.SShareErrNum
			rskErrInfoNum := make([]big.Int, 1)
			rskErrInfoNum[0] = *big.NewInt(0).SetInt64(int64(msStep.SShareErrNum))
			err = msStep.mpcResult.SetValue(keyErrNum, rskErrInfoNum)
			if err != nil {
				log.SyslogErr("MpcSStepUni::HandleMessage mpcResult.SetValue save fail", " err", err.Error(), "key", keyErrNum, "value", rskErrInfoNum, "ctx", msStep.GetMpcContextId())
			} else {
				if msStep.SShareErrNum > 0 {
					log.SyslogWarning("MpcSStepUni::HandleMessage mpcResult.SetValue save success", "key", keyErrNum,
						"value", hexutil.Encode(rskErrInfoNum[0].Bytes()), "ctx", msStep.GetMpcContextId())
				}
			}
		}

	}

	for key, _ := range mapOKIndex {
		msStep.sshareOKIndex = append(msStep.sshareOKIndex, key)
	}

	return true
}

func (msStep *MpcSStepUni) FinishStep(result mpcprotocol.MpcResultInterface, mpc mpcprotocol.StoremanManager) error {
	log.SyslogDebug("MpcSStepUni::FinishStep begin", "ctx", msStep.GetMpcContextId())
	err := msStep.BaseMpcStep.FinishStep()

	// save index for incentive and slash

	_, grpIdString, _ := msStep.BaseStep.GetGrpId()

	allIndex, _ := osmconf.GetOsmConf().GetGrpElemsInxes(grpIdString)
	// add leader in incentive list, because leader collect the sshare data, once leader collects threshold signature,
	// it will ignore the data from itself.
	findLeader := false
	for _, myIndex := range msStep.sshareOKIndex {
		if myIndex == uint16(0) {
			findLeader = true
			break
		}
	}
	if !findLeader {
		msStep.sshareOKIndex = append(msStep.sshareOKIndex, uint16(0))
	}
	tempIndex := osmconf.Difference(*allIndex, msStep.sshareOKIndex)
	msStep.sshareNOIndex = osmconf.Difference(tempIndex, msStep.sshareKOIndex)

	//okIndex := make([]big.Int, len(msStep.sshareOKIndex))
	//koIndex := make([]big.Int, len(msStep.sshareKOIndex))
	//noIndex := make([]big.Int, len(msStep.sshareNOIndex))

	okIndex := make([]big.Int, 0)
	koIndex := make([]big.Int, 0)
	noIndex := make([]big.Int, 0)

	log.SyslogDebug("MpcSStepUni::FinishStep", "allIndex", allIndex, "ctx", msStep.GetMpcContextId())
	log.SyslogDebug("MpcSStepUni::FinishStep", "sshareOKIndex", msStep.sshareOKIndex, "ctx", msStep.GetMpcContextId())
	log.SyslogDebug("MpcSStepUni::FinishStep", "sshareKOIndex", msStep.sshareKOIndex, "ctx", msStep.GetMpcContextId())
	log.SyslogDebug("MpcSStepUni::FinishStep", "sshareNOIndex", msStep.sshareNOIndex, "ctx", msStep.GetMpcContextId())

	for _, value := range msStep.sshareOKIndex {
		okIndex = append(okIndex, *big.NewInt(0).SetInt64(int64(value)))
	}

	for _, value := range msStep.sshareKOIndex {
		koIndex = append(koIndex, *big.NewInt(0).SetInt64(int64(value)))
	}

	for _, value := range msStep.sshareNOIndex {
		noIndex = append(noIndex, *big.NewInt(0).SetInt64(int64(value)))
	}

	msStep.mpcResult.SetValue(mpcprotocol.SOKIndex, okIndex)
	msStep.mpcResult.SetValue(mpcprotocol.SKOIndex, koIndex)
	msStep.mpcResult.SetValue(mpcprotocol.SNOIndex, noIndex)

	okBig, _ := osmconf.BuildDataByIndexes(&okIndex)
	koBig, _ := osmconf.BuildDataByIndexes(&koIndex)
	noBig, _ := osmconf.BuildDataByIndexes(&noIndex)
	log.SyslogInfo("MpcSStepUni::FinishStep",
		"sshareOKIndex", hex.EncodeToString(okBig.Bytes()),
		"sshareKOIndex", hex.EncodeToString(koBig.Bytes()),
		"sshareNOIndex", hex.EncodeToString(noBig.Bytes()),
		"ctx", msStep.GetMpcContextId())

	if err != nil {
		_, retHash := msStep.BaseMpcStep.GetSignedDataHash(result)
		msStep.BaseMpcStep.ShowNotArriveNodes(retHash, mpc.SelfNodeId())

		return err
	}

	for i := 0; i < msStep.signNum; i++ {
		pointer := msStep.messages[i].(*mpcSGeneratorUni)
		// MpcS

		log.SyslogInfo("MpcSStepUni::FinishStep", "s", hexutil.Encode(pointer.result.Bytes()), "ctx", msStep.GetMpcContextId())

		err = result.SetValue(msStep.resultKeys[i], []big.Int{pointer.result})
		if err != nil {
			log.SyslogErr("MpcSStepUni::FinishStep", "MpcSStep.FinishStep, SetValue fail. err", err.Error(), "ctx", msStep.GetMpcContextId())
			return err
		}
	}

	log.SyslogDebug("MpcSStepUni::FinishStep succeed", "ctx", msStep.GetMpcContextId())
	return nil
}
